using TestCases.Assertions;
using TestCases.CompareAssert.Core;
using TestCases.CompareAssert.Core.Comparers;
using TestCases.CompareAssert.Core.Comparers.Interfaces;
using TestCases.CompareAssert.Core.Interfaces;
using TestCases.CompareAssert.Interfaces;
using TestCases.CompareAssert.Settings;
using TestCases.CompareAssert.Settings.Interfaces;
using TestCases.Utils;

namespace TestCases.CompareAssert
{
    public class CompareAssertion : ICompareAssertion
    {
        private readonly IAssertionOptions _options;
        private readonly IObjectComparer _comparer;

        public CompareAssertion(IAssertionOptions options, IObjectComparer comparer)
        {
            _options = options;
            _comparer = comparer;
        }
        
        public CompareAssertion(IObjectComparer comparer) : this(CAssertOptions.DefaultInstance, comparer)
        {
        }

        public static CompareAssertion DefaultInstance
        {
            get { return DefaultInitializer.Instance; }
        }

        public ICompareResult Compare(object expected, object actual)
        {
            return _comparer.Compare(new CompareContext(
                _options.Factory,
                ComparerObject.RootObject(expected, "expected"),
                ComparerObject.RootObject(actual, "actual"))).GetResult();
        }

        public void CompareStatusIs(CompareStatus expectedStatus, object expected, object actual)
        {
            var actualResult = Compare(expected, actual);
            var actualStatus = actualResult.Status;
            var isSuccess = expectedStatus == actualStatus;
            TraceResultByRules(actualResult, isSuccess);
            Assert(isSuccess, "Statuses are different. Expected:<{Expected}>. Actual:<{Actual}>.".
                FormatWith(new {Expected = expectedStatus, Actual = actualStatus}));
        }

        public void ObjectsAreEquals(object expected, object actual)
        {
            CompareStatusIs(CompareStatus.Equals, expected, actual);
        }

        public void ObjectsAreNotEquals(object expected, object actual)
        {
            CompareStatusIs(CompareStatus.NotEquals, expected, actual);
        }

        public void ObjectsCantBeCompared(object expected, object actual)
        {
            CompareStatusIs(CompareStatus.NotApplicable, expected, actual);
        }

        private void Assert(bool expr, string message)
        {
            _options.Assertion.Assert(expr, message);
        }

        private void Trace(string message)
        {
            _options.Assertion.Trace(message);
        }

        private void TraceResult(ICompareResult result)
        {
            Trace(_options.Formatter.FormatResult(result));
        }

        private void TraceResultByRules(ICompareResult result, bool isSuccesful)
        {
            var rules = _options.TraceRules;
            switch (rules)
            {
                case TraceRules.All:
                    TraceResult(result);
                    break;

                case TraceRules.Equals:
                    if (result.Status == CompareStatus.Equals)
                    {
                        TraceResult(result);
                    }
                    break;

                case TraceRules.NotEquals:
                    if (result.Status == CompareStatus.NotEquals)
                    {
                        TraceResult(result);
                    }
                    break;

                case TraceRules.NotApplicable:
                    if (result.Status == CompareStatus.NotApplicable)
                    {
                        TraceResult(result);
                    }
                    break;

                case TraceRules.Successful:
                    if (isSuccesful)
                    {
                        TraceResult(result);
                    }
                    break;

                case TraceRules.Failed:
                    if (!isSuccesful)
                    {
                        TraceResult(result);
                    }
                    break;
            }
        }

        private static class DefaultInitializer
        {
// ReSharper disable EmptyConstructor

            internal static readonly CompareAssertion Instance = 
                new CompareAssertion(CAssertOptions.DefaultInstance, DefaultComparer.Instance);

            static DefaultInitializer()
// ReSharper restore EmptyConstructor
            {
            }
        }

    }
}